'''
Created on Apr 10, 2012

@author: tel
'''

import config
#global vars: META, DB

from sqlalchemy import Table, Column, Integer, Unicode, MetaData, create_engine, String, ForeignKey, Float, PickleType
from sqlalchemy.orm import mapper, relationship, backref
from sqlalchemy.schema import Index
from data.data import Data

from sqlalchemy.dialects.mysql import FLOAT

def hasColumn(table, column_key):
    return table.c.has_key(column_key)

class DataSQLMetaclass(type): 
    '''
    ugh... metaclasses
    this was added to deal with initializing the class attributes, especially the mutables, in all of the children of DataSQL
    otherwise, you end up with every child and every instance of every child sharing the same lists passed by reference
    basic form stolen from http://stackoverflow.com/questions/100003/what-is-a-metaclass-in-python
    '''
    def __new__(cls, name, bases, dct):
        augmented_attrs = (('dtype','default'),
                           ('parent_dtype', None),
                           ('super_parent_dtypes', []),
                           ('ChildDClass', None),
                           ('SubChildDClasses', []))
        for attr, val in augmented_attrs:
            setattr(cls, attr, val) #screw the collisions and damn the torpedoes!
        dct['instances'] = []
        return super(DataSQLMetaclass, cls).__new__(cls, name, bases, dct)

class DataSQL(Data):
    '''
    needs a dtype, parent_dtype, and ChildDClass class attributes in order to function properly
    need to initialize config.DB and config.META before using this class
    '''
    __metaclass__ = DataSQLMetaclass
    #dtype = 'default'
    #parent_dtype = None
    #super_parent_dtypes = []
    #ChildDClass = None
    #SubChildDClasses = []
    #instances = []
    
    def __init__(self, **kwargs):
        super(DataSQL, self).__init__(dtype=self.__class__.dtype, **kwargs)
        #self.BuildTable(self)
    
    def reinit(self):
        self.__init__(data_tuples=self.GetDataTuples())
    
    @classmethod
    def Store(cls, data_tuples):
        cls.instances.append(data_tuples)
        
    @classmethod
    def Table(cls, meta=None, db=None, reloadt=False, standalone=False):
        if getattr(cls, 'table', False) == False:
            if meta!=None:
                cls.meta = meta
            if db!=None:
                cls.db = db
            if reloadt:
                cls.ReloadTable(standalone)
            elif cls.dtype in meta.tables:
                cls.RejoinTable(standalone)
            else:        
                cls.BuildTable(cls.instances[0], standalone)
                cls.CreateTable()
    
    @classmethod
    def SQLize(cls, meta=None):
        if meta!=None:
            cls.meta = meta
        for i, instance in enumerate(cls.instances):
            cls.instances[i] = cls(data_tuples=instance)

    @classmethod
    def Pop(cls):
        return cls.instances.pop()
            
    @classmethod
    def Purge(cls):
        cls.instances = []
    
    @classmethod
    def BuildTable(cls, data_tuples, standalone):
        meta = cls.meta
        cls.table = Table(cls.dtype, meta, Column('id', Integer, primary_key = True, index=True))
        for columnName, value in data_tuples:
            if not hasColumn(cls.table, columnName):
                if isinstance(value, int):
                    cls.table.append_column(Column(columnName, Integer()))
                elif isinstance(value, float):
                    cls.table.append_column(Column(columnName, Float(precision=6)))
                elif isinstance(value, str):
                    try:    #If it's an integer, store as an integer. If it's a float, store as a float. Otherwise, store as a string 
                        int(value)
                        cls.table.append_column(Column(columnName, Integer()))
                    except ValueError:
                        try:
                            float(value)
                            cls.table.append_column(Column(columnName, Float(precision=6)))
                        except ValueError:
                            cls.table.append_column(Column(columnName, String(50)))
                else:
                    cls.table.append_column(Column(columnName, PickleType()))
        reldict = {}
        if standalone!=True:
            if cls.parent_dtype != None:
                cls.table.append_column(Column(cls.parent_dtype+'_id', Integer, ForeignKey(cls.parent_dtype+'.id'), index=True))
                for super_parent_dtype in cls.super_parent_dtypes:
                    cls.table.append_column(Column(super_parent_dtype+'_id', Integer, ForeignKey(super_parent_dtype+'.id'), index=True)) 
            if cls.ChildDClass:
                childlist = [cls.ChildDClass] + cls.SubChildDClasses
            else:
                childlist = []
            for Child in childlist:
                reldict[Child.dtype+'s']=relationship(Child, backref=cls.dtype) #, primaryjoin=Child.table.c.__getattribute__(cls.dtype+'_id')==cls.table.c.id
        if reldict:
            mapper(cls, cls.table, properties=reldict)
        else:
            mapper(cls, cls.table)
    
    @classmethod
    def ReloadTable(cls, standalone, override_columns=None):
        meta = cls.meta
        if override_columns==None:
            override_columns=[]
        cls.table = Table(cls.dtype, meta, *override_columns, autoload=True)
        reldict = {}
        if standalone!=True:
            if cls.parent_dtype != None:
                cls.table.append_column(Column(cls.parent_dtype+'_id', Integer, ForeignKey(cls.parent_dtype+'.id'), index=True))
                for super_parent_dtype in cls.super_parent_dtypes:
                    cls.table.append_column(Column(super_parent_dtype+'_id', Integer, ForeignKey(super_parent_dtype+'.id'), index=True)) 
            if cls.ChildDClass:
                childlist = [cls.ChildDClass] + cls.SubChildDClasses
            else:
                childlist = []
            for Child in childlist:
                reldict[Child.dtype+'s']=relationship(Child, backref=cls.dtype) #, primaryjoin=Child.table.c.__getattribute__(cls.dtype+'_id')==cls.table.c.id
        if reldict:
            mapper(cls, cls.table, properties=reldict)
        else:
            mapper(cls, cls.table)
            
    @classmethod
    def RejoinTable(cls, standalone):
        meta = cls.meta
        cls.table = meta.tables[cls.dtype]
        reldict = {}
        if standalone!=True:
            if cls.ChildDClass:
                childlist = [cls.ChildDClass] + cls.SubChildDClasses
            else:
                childlist = []
            for Child in childlist:
                reldict[Child.dtype+'s']=relationship(Child, backref=cls.dtype) #, primaryjoin=Child.table.c.__getattribute__(cls.dtype+'_id')==cls.table.c.id
        if reldict:
            mapper(cls, cls.table, properties=reldict)
        else:
            mapper(cls, cls.table)
      
    @classmethod
    def CreateTable(cls):
        db = cls.db
        cls.table.drop(db, checkfirst=True)
        cls.table.create(db)
            
def MakeDataSQL(name, dtype, ChildDClass=None, parent_dtype=None):
    '''
    factory function for creating data types with associated sql tables
    '''
    return type(name, (DataSQL,), dict(dtype=dtype, ChildDClass=ChildDClass, parent_dtype=parent_dtype))
